#require 'base64'
#see actionpack/lib/action_controller/http_authentication.rb
#and see http://agilewebdevelopment.com/plugins/htpasswd
#DigestAuthentication::Digest
module DigestAuthentication
  module Digest       
        def kdigest_http_authenticate_or_request(realm = "Application", &login_procedure)
          kdigest_http_authenticate(&login_procedure) || kdigest_http_request_authentication(realm)
        end

        def kdigest_http_authenticate(&login_procedure)
          ::DigestAuthentication::DigestCore.authenticate(self, &login_procedure)
        end

        def kdigest_http_request_authentication(realm = "Application")
          ::DigestAuthentication::DigestCore.authentication_request_digest(self, realm)
        end
      
        def kdigest_get_digest(username , password , realm = "Application")
            ::DigestAuthentication::DigestCore.get_digest_value(self.request,username , password , realm)
        end
    end
    module DigestCore extend self
      PARAMETER_VALID_KEY_MAPPINGS = {
            :username  => :user,
            :realm     => :realm,
            :qop       => :qop,
            :algorithm => :algorithm,
            :uri       => :uri,
            :nonce     => :nonce,
            :nc        => :nc,
            :cnonce    => :cnonce,
            :response  => :response,
        }
        
        
      def authenticate(controller, &login_procedure)
        unless authorization(controller.request).blank?
          login_procedure.call(*user_name_and_digest(controller.request))
        end
      end
      
      def authentication_request_digest(controller , realm)   
            controller.headers["WWW-Authenticate"] = %Q|Digest realm="%s", nonce="%s", algorithm=%s, qop=%s| % [realm, nonce, algorithm, qop] 
            controller.send! :render, :text => "HTTP Digest: Access denied.\n", :status => :unauthorized
      end
      
      def get_digest_value(request , username , password , realm)
            infohash = get_authentication_infos(request)
            a1 = ::Digest::MD5.hexdigest([username , realm , password] * ":")
            if request.get?
                a2 = ::Digest::MD5.hexdigest("GET:#{request.path}")
            elsif request.post?
                a2 = ::Digest::MD5.hexdigest("POST:#{request.path}")
            else
                raise "DigestAuthentication Error Method , only get and put is permitted"
            end
            ::Digest::MD5.hexdigest([a1 , infohash[:nonce] , infohash[:nc] ,infohash[:cnonce] , infohash[:qop] , a2] * ":")
      end
        
      def user_name_and_digest(request)
         infohash = get_authentication_infos(request)
         [infohash[:user] , infohash[:response]] 
      end
        
      
      #-------------------------------
      def nonce
            time  = Time.now.to_i
            etag  = random(16)
            pkey  = random(16)
            Base64.encode64("%s:%s:%s" % [time, etag, pkey]).chomp
        end

        def algorithm
            "MD5"
        end

        def qop
            "auth"
        end

        def random(size = 16, array = nil)
            array ||= (0..9).to_a + ('a'..'f').to_a
            (0...size).map{array[rand(array.size)]}.join
        end 
        
        def authorization(request)
         request.env['HTTP_AUTHORIZATION']   ||
         request.env['X-HTTP_AUTHORIZATION'] ||
         request.env['X_HTTP_AUTHORIZATION'] ||
         request.env['REDIRECT_X_HTTP_AUTHORIZATION']
       end
        
        def extract_header(hash)
          [
            'X-HTTP_AUTHORIZATION',          # for Apache/mod_rewrite
            'REDIRECT_X_HTTP_AUTHORIZATION', # for Apache2/mod_rewrite
            'Authorization',                 # for Apace/mod_fastcgi with -pass-header Authorization 
            'HTTP_AUTHORIZATION',            # this is the regular location 
          ].map{|name| hash[name]}.compact.first
        end
        
        def get_authentication_infos(request)
            header = extract_header(request.env)
            type, data = header.to_s.split(' ', 2)
            options = HashWithIndifferentAccess.new
            ret = data.to_s.split(/,\s*/).each do |query|
                 key, val = query.split('=', 2)
                 if valid_key_name = PARAMETER_VALID_KEY_MAPPINGS[key.to_s.intern]
                  options[valid_key_name] = val ? val.to_s.delete('"') : nil
#              ActionController::Base.logger.debug("parse: %s => %s" % [valid_key_name,options[valid_key_name]])
                 end
           end
           options
        end
  end
end
